package com.yugao.fintech.antelope.pay.payment.channel.wxpay;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson2.JSONObject;
import com.github.binarywang.wxpay.service.WxPayService;
import com.yugao.fintech.antelope.base.utils.ExcelContentType;
import com.yugao.fintech.antelope.base.utils.ExcelUtils;
import com.yugao.fintech.antelope.pay.common.constants.Cs;
import com.yugao.fintech.antelope.pay.constant.IfCodeEnum;
import com.yugao.fintech.antelope.pay.payment.channel.AbstractBillService;
import com.yugao.fintech.antelope.pay.payment.channel.wxpay.utils.WxpayV3Util;
import com.yugao.fintech.antelope.pay.payment.model.bo.MchAppConfigContext;
import com.yugao.fintech.antelope.pay.payment.model.bo.WxServiceWrapper;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.bill.BillQueryRQ;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.bill.BillQueryRS;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.bill.WxBillQueryRS;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.bill.WxBillRS;
import com.yugao.fintech.antelope.pay.payment.service.ConfigContextQueryService;
import com.yugao.fintech.framework.assistant.utils.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import static com.wechat.pay.java.core.http.UrlEncoder.urlEncode;


@Slf4j
@Service
public class WxpayBillService extends AbstractBillService {
    @Autowired
    private ConfigContextQueryService configContextQueryService;
    @Autowired
    private HttpServletResponse response;

    @Override
    public IfCodeEnum getIfCode() {
        return IfCodeEnum.WXPAY;
    }

    /**
     * 因为下载的账单文件可能会很大，为了平衡系统性能和签名验签的实现成本，账单下载API 被分成了两个步骤：
     * <p>
     * /v3/bill/tradebill 申请账单下载链接，并获取账单摘要。</br>
     * /v3/billdownload/file 账单文件下载，请求需签名但应答不签名。 </br>
     * SDK 提供了 HttpClient.download() 方法。它返回账单的输入流。开发者使用完输入流后，应自主关闭流。 </br>
     * </p>
     */
    @Override
    public BillQueryRS queryTradeBill(BillQueryRQ bizRQ, MchAppConfigContext mchAppConfigContext) throws Exception {
        WxServiceWrapper wxServiceWrapper = (WxServiceWrapper) configContextQueryService
                .getClientWrapper(this.getIfCode(), mchAppConfigContext);
        if (Cs.PAY_IF_VERSION.WX_V3.equals(wxServiceWrapper.getApiVersion())) {
            WxPayService wxPayService = wxServiceWrapper.getWxPayService();
            /* 获取交易账单 */
            JSONObject reqJSON = new JSONObject();
            reqJSON.put("bill_date", urlEncode(bizRQ.getBillDate()));
            reqJSON.put("bill_type", "ALL");
            JSONObject queryTradeBill = WxpayV3Util.queryTradeBill(reqJSON, wxPayService);
            WxBillRS wxBillRS = queryTradeBill.to(WxBillRS.class);

            /* 使用第一步获取的账单摘要校验文件的完整性 */
            InputStream checkIs = null;
            InputStream saveIs = null;
            try (InputStream is = wxPayService.downloadV3(wxBillRS.getDownloadUrl())) {
                byte[] bytes = IoUtil.readBytes(is);
                // 防止流读取一次不能再次读取问题
                checkIs = new ByteArrayInputStream(bytes);
                saveIs = new ByteArrayInputStream(bytes);
                String hex = DigestUtils.sha1Hex(checkIs);
                if (wxBillRS.getHashValue().equals(hex)) {
                    log.info("文件完整性校验成功, 开始保存账单");
                    return toQueryBillRS(IoUtil.read(saveIs, StandardCharsets.UTF_8));
                } else {
                    throw new BizException("文件完整性校验失败");
                }
            } finally {
                if (Objects.nonNull(checkIs)) {
                    checkIs.close();
                }
                if (Objects.nonNull(saveIs)) {
                    saveIs.close();
                }
            }
        } else {
            throw new BizException("不支持的微信支付版本");
        }
    }

    @Override
    public void exportTradeBill(BillQueryRQ req, MchAppConfigContext mchAppConfigContext) throws Exception {
        WxBillQueryRS rs = (WxBillQueryRS) queryTradeBill(req, mchAppConfigContext);
        String fileName = "微信账单-" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".xls";
        ExcelUtils.exportExcel(response, ExcelContentType.XLS, fileName, WxBillQueryRS.Detail.class, rs.getDetailList());
    }

    private static BillQueryRS toQueryBillRS(String data) {
        WxBillQueryRS rs = new WxBillQueryRS();
        rs.setChannelContent(data);

        int i = data.indexOf("`");
        int j = data.indexOf("总");

        String substring = data.substring(i, j - 2);
        String[] temp = substring.split("\n");

        List<WxBillQueryRS.Detail> list = new ArrayList<>();
        for (String s : temp) {
            String[] payment = s.replace("`", "").split(",");
            WxBillQueryRS.Detail bean = new WxBillQueryRS.Detail();
            for (int p = 0; p < payment.length; p++) {
                bean.setTradeTime(payment[0]);
                bean.setAppId(payment[1]);
                bean.setMchId(payment[2]);
                bean.setMchAppId(payment[3]);
                bean.setDeviceInfo(payment[4]);
                bean.setTransactionId(payment[5]);
                bean.setOutTradeNo(payment[6]);
                bean.setOpenId(payment[7]);
                bean.setTradeType(payment[8]);
                bean.setTradeStatus(payment[9]);
                bean.setPayBank(payment[10]);
                bean.setMoneyType(payment[11]);
                bean.setOrderPay(payment[12]);
                bean.setVoucherAmount(payment[13]);
                bean.setRefundNumber(payment[14]);
                bean.setOutRefundNo(payment[15]);
                bean.setRefundAmount(payment[16]);
                bean.setRefundAmountVoucher(payment[17]);
                bean.setRefundsType(payment[18]);
                bean.setRefundsStatus(payment[19]);
                bean.setCommodityName(payment[20]);
                bean.setDataPacket(payment[21]);
                bean.setServiceCharge(payment[22]);
                bean.setRate(payment[23]);
                bean.setOrderAmount(payment[24]);
                bean.setApplicationRefundAmount(payment[25]);
            }
            list.add(bean);
        }
        rs.setDetailList(list);
        return rs;
    }
}
